home *** CD-ROM | disk | FTP | other *** search
/ Shareware Super Platinum 8 / Shareware Super Platinum 8.iso / mac / BACKUP / SYNC.ZIP;1 / SYNC.PRG < prev   
Encoding:
Text File  |  1993-09-17  |  31.3 KB  |  726 lines

  1. /*      SYNC.PRG                version 1.1                     0/09/1993
  2.  
  3.         File synchronization utility by Mickey R. Burnette
  4.         (c) 1993 Mickey R. Burnette
  5.         (c) 1993 SofKinetics, Inc.
  6.  
  7.         Program placed in the public domain 6/13/1993 for Non-Commercial use
  8.         only and provided that all copyright notices remain intact.
  9.  
  10.         SofKinetics, Inc. nor Mickey R. Burnette make any claims or 
  11.         warranties for this product.  User shall determine suitability
  12.         of purpose and assume all liability for product use.
  13.  
  14.         Compile with CLIPPER 5.2b
  15.                 clipper sync /n /l /m
  16.         Link with RLINK
  17.                 rlink fi sync
  18.  
  19.         Modification History:
  20.         6/13/93 Version 1.0a corrected time stamp compares
  21.         6/22/93 Version 1.0b added aWriteOut array. Batch file created only
  22.                 when array is not empty.
  23.         9/09/93 Version 1.1 added Daisy-Chain target capability for mirrored 
  24.                 directory structures. Added DOS wildcards to FileName field
  25. */
  26.  
  27. // Subscripts for DIRECTORY() array
  28. #define F_NAME        1
  29. #define F_SIZE        2
  30. #define F_DATE        3
  31. #define F_TIME        4
  32. #define F_ATTR        5
  33.  
  34. // Define file-wide static variables
  35. STATIC aWriteOut := {}, eTime := {}, lTime := .F., lFlag := .F.
  36.  
  37. Function sync( cLine )
  38.         #define LF              chr(10)
  39.         #define FF              chr(12)
  40.         #define CR              chr(13)
  41.         #define CRLF            CR + LF
  42.  
  43.         local aMakDir := {}, cOutBatch := "UPDATE.BAT"
  44.         local aDirSrc := {}, cWildSrc, cOldSrc, nSrcPtr, cSrcFile
  45.         local aSrc := {} // for subarray
  46.         local aDirTgt := {}, cWildTgt, cOldTgt, nTgtPtr, cTgtFile, cTgt
  47.         local aTgt := {} // for subarray
  48.         local lDoCopy := .F., aMakeArray, nLooper, lReportMode := .F., lVideo := .F.
  49.         local lQuiet := .F., nCount := 0, lOverride := .F., dBaseEdit := .F.
  50.         local cSHADOWFl, cWildSHADOW, cFileName, aFileList, aTempList, i
  51.  
  52.         // Convert runline to all upper case
  53.         if ! empty( cLine )
  54.                 cLine := upper( cLine )
  55.                 if "/T" $ cLine
  56.                         aadd( eTime, "Start:  " + time() )
  57.                         lTime := .T.
  58.                 endif
  59.         endif
  60.  
  61.         // check for runline help
  62.         if ! empty( cLine ) .and. "?" $ cLine
  63.                 clear screen
  64.                 ?? "File Synchronization Utility Version 1.1          (c) 1993 by SofKinetics, Inc."
  65.                 ?  "Public Domain Version:  Free Use License Granted.  Commerical Rights Reserved."
  66.                 ?
  67.                 ? "     Valid run-line parameters:"
  68.                 ?
  69.                 ? "     /RP     Report to printer date/time of updates"
  70.                 ? "     /RV     Report to screen date/time of updates"
  71.                 ? "     /Q      quiet mode; no video output displayed"
  72.                 ? "     /O      Override user intervention flag"
  73.                 ? "     /M      Specify location for database"
  74.                 ? "     /E      Edit synchronization database"
  75.                 ? "     /F      FileSpec passed, see below"
  76.                 ? "     /T      Output event timing"
  77.                 ? "     /?      This help screen"
  78.                 ?
  79.                 ? " *   Specify multiple parameters together: /Q/O"
  80.                 ? " *   When using /F, this parameter MUST be specified last"
  81.                 ? "             For example:    /O/Q/Fmyfile.bat"
  82.                 ? " *   In /F is not specified, the default file is UPDATE.BAT"
  83.                 ?
  84.                 ? " *   Note: Batch file is ONLY created when dictionary search"
  85.                 ? "           identifies a file synchronization mismatch. Erase"
  86.                 ? "           the batch file prior to running SYNC and test for"
  87.                 ? "           its existence afterward to determine update need."
  88.                 quit
  89.         endif
  90.  
  91.         // check for MemVar edit
  92.         if ! empty( cLine ) .and. "/M" $ cLine
  93.                 mEdit()
  94.                 quit
  95.         endif
  96.  
  97.         // check runline for quiet mode
  98.         if ! empty( cLine ) .and. "/Q" $ cLine
  99.                 lQuiet := .T.
  100.         endif
  101.  
  102.         // check runline for Over-Ride
  103.         if ! empty( cLine ) .and. "/O" $ cLine
  104.                 lOverride := .T.
  105.         endif
  106.  
  107.         // check runline for Edit Mode
  108.         if ! empty( cLine ) .and. "/E" $ cLine
  109.                 dBaseEdit := .T.
  110.         endif
  111.  
  112.         // check runline for Report Mode P
  113.         if ! empty( cLine ) .and. "/RP" $ cLine
  114.                 lReportMode := .T.
  115.                 if ! isprinter()
  116.                         if alert("Cannot print to printer due;" + ;
  117.                                  "to printer error! Output to;" + ;
  118.                                  "video monitor instead?", ;
  119.                                  {"YES","Quit"} ) == 1
  120.                                 lVideo := .T.
  121.                         else
  122.                                 quit
  123.                         endif
  124.                 endif
  125.         endif
  126.  
  127.         // check runline for Report Mode V
  128.         if ! empty( cLine ) .and. "/RV" $ cLine
  129.                 lReportMode := .T.
  130.                 lVideo := .T.
  131.         endif
  132.  
  133.         // check for alternate batch file name
  134.         if ! empty( cLine ) .and. "/F" $ cLine
  135.                  cOutBatch := substr(cLine, at("/F", cLine) + 2)
  136.                  if empty( cOutBatch )
  137.                         cOutBatch := "UPDATE.BAT"
  138.                  endif
  139.         endif
  140.         
  141.         // get location of database from user's root
  142.         if ! file( "C:\SYNC.MEM" )
  143.                 alert("ERROR! Dictionary pointer cannot;" + ;
  144.                       "be located on ROOT drive.", {"Press ENTER"})
  145.                 if ! mEdit()
  146.                     ERRORLEVEL(1)
  147.                     quit
  148.                 endif
  149.         endif
  150.  
  151.         restore from c:\sync additive // MEMVAR->D_LOOK
  152.  
  153.         /* Verify that dictionary is available for use.  This file could be
  154.            on a network, so return a DOS error if not found. In this manner,
  155.            autoexec.bat can branch around calling UPDATE.BAT if an error is 
  156.            returned: for example, when a notebook is not docked, user not
  157.            logged into network, etc.
  158.         */
  159.         if ! file( D_LOOK )
  160.                 ERRORLEVEL(1)
  161.                 quit
  162.         endif
  163.  
  164.         // begin main processing ...
  165.         if ! lQuiet
  166.                 clear screen
  167.                 ? "File Synchronization Utility V1.1/Public Domain (c) 1993 by SofKinetics, Inc."
  168.                 ?
  169.         endif
  170.  
  171.         /* Open dictionary file .  This is a standard xBase file with
  172.            the following file structure:
  173.                 FILENAME        CHARACTER       12
  174.                 SOURCE_DIR      CHARACTER       40
  175.                 TARGET_DIR      CHARACTER       40
  176.                 SHADOW_DIR      CHARACTER       40
  177.                 INTERACT        LOGICAL         
  178.                 LASTDATE        DATE
  179.                 LASTTIME        CHARACTER       8
  180.         */
  181.  
  182.         use ( D_LOOK ) new alias dict
  183.  
  184.         /* If editing database, invoke editor, remove deleted records, purge and
  185.            close DOS file buffers, and exit routine.
  186.         */
  187.         if dBaseEdit
  188.                 dEdit()
  189.                 close all
  190.                 quit
  191.         endif
  192.  
  193.         /* Index on source directory.  Since this routine is designed to run on a
  194.            network, we would like to minimize the reading of network directories. The
  195.            sort places the dictionary in source file order to allow us to only read
  196.            from the network on directory changes.
  197.         */
  198.         index on SOURCE_DIR to c:\temp.$$$
  199.         go top
  200.  
  201.         // If /R was used to indicate Report Mode, call routine and quit.
  202.         if lReportMode
  203.                 report( lVideo )
  204.                 close all
  205.                 quit
  206.         endif
  207.  
  208.         aFileList := {}
  209.         aadd( aWriteOut, "@ECHO OFF" )
  210.         aadd( aWriteOut, "REM File Synchronization Utility V1.1" )
  211.         cOldSrc := ""           //force initialization source path
  212.         cOldTgt := ""           //force initialization target path
  213.  
  214.         if lTime
  215.                 aadd( eTime, "Begin Do" + time() )
  216.         endif
  217.  
  218.         Do while !eof()
  219.                 // source should be as 'drive:path\' but could be 'drive:' only
  220.                 if right(alltrim(dict->SOURCE_DIR), 1) == "\"
  221.                         cWildSrc := alltrim( dict->SOURCE_DIR ) + "*.*"
  222.                 else
  223.                         cWildSrc := alltrim( dict->SOURCE_DIR ) + "\*.*"
  224.                 endif
  225.  
  226.                 // load source directory into source array if necessary
  227.                 if cOldSrc != cWildSrc
  228.                         aDirSrc := directory( cWildSrc )
  229.                         cOldSrc := cWildSrc
  230.                 endif
  231.  
  232.                 // target should be as 'drive:path\' but could be 'drive:' only
  233.                 if right(alltrim(dict->TARGET_DIR), 1) == "\"
  234.                         cWildTgt := alltrim( dict->TARGET_DIR ) + "*.*"
  235.                 else
  236.                         cWildTgt := alltrim( dict->TARGET_DIR ) + "\*.*"
  237.                 endif
  238.  
  239.                 // load target directory into target array if necessary
  240.                 if cOldTgt != cWildTgt
  241.                         aDirTgt := directory( cWildTgt )
  242.                         cOldTgt := cWildTgt
  243.                 endif
  244.  
  245.                 // SHADOW should be as 'drive:path\' but could be 'drive:' only
  246.                 if right(alltrim(dict->SHADOW_DIR), 1) == "\"
  247.                         cWildSHADOW := alltrim( dict->SHADOW_DIR ) + "*.*"
  248.                 else
  249.                         cWildSHADOW := alltrim( dict->SHADOW_DIR ) + "\*.*"
  250.                 endif
  251.  
  252.                 aFileList := {}
  253.  
  254.                 /* Check for wildcards in file specification.  Upon exiting this logic
  255.                    array aFileList will have one or more added entries.
  256.                 */
  257.                 if alltrim( dict->FILENAME ) == "*.*"
  258.                         aeval( aDirSrc, {| e | aadd( aFileList, e[F_NAME] )})
  259.  
  260.                 elseif "*" $ dict->FILENAME .or. "?" $ dict->FILENAME
  261.                         if right(alltrim(dict->SOURCE_DIR), 1) == "\"
  262.                                 aTempList := directory( alltrim( dict->SOURCE_DIR ) + ;
  263.                                         alltrim( dict->FILENAME ))
  264.                         else
  265.                                 aTempList := directory( alltrim( dict->SOURCE_DIR ) + ;
  266.                                         "\" + alltrim( dict->FILENAME ))
  267.                         endif
  268.  
  269.                         aeval( aTempList, { | aEl | aadd( aFileList, aEl[F_NAME] )})
  270.                         aTempList := {} // finished with temp array
  271.                 else
  272.                         aadd( aFileList, dict->FILENAME )
  273.                 endif
  274.  
  275.               /* create a loop state to handle wildcard situations.  Unless an * or ?
  276.                  was utilized in the filename, the aFileList will be an array of len=1.
  277.                  Otherwise, aFileList will contain a list of entries spawned by the
  278.                  filename specification.
  279.               */
  280.               for i = 1 to len( aFileList )
  281.                 iif( i > 0, cFileName := aFileList[ i ], cFileName := dict->FILENAME )
  282.  
  283.                 /* There are now two multi-dimensional arrays; ie, arrays of arrays
  284.                    Each sub-array has the structure:
  285.                         F_NAME [1]
  286.                         F_SIZE [2]
  287.                         F_DATE [3]
  288.                         F_TIME [4]
  289.                         F_ATT  [5]
  290.  
  291.                    We need only to scan the SRC array for the filename and
  292.                    extract the time and date.  Then we scan the TGT array.  If
  293.                    the filename is found in the target, compare the time and
  294.                    dates and copy only if SRC is newer than TGT.  If filename
  295.                    is not found in the TGT, simply copy it!  If a TGT filename needs
  296.                    to be updated, the check to see if a "shadow" directory was
  297.                    specified: if so, the batch file will include a copy from the TGT to
  298.                    the SHADOW.  This scenario attempts to reduce network load if the
  299.                    source directory is a virtual drive.
  300.  
  301.                    To do the scan, we'll utilize Clipper's ability to evaluate code-
  302.                    blocks for each array element.  The function is aEVAL() and the 
  303.                    code block, | |, will be passed two parameters: the value of the
  304.                    current element (in this case a multi-dimensional array) and the
  305.                    current offset into the array. The remaining conditional statement,
  306.                    that is the IF() will be able to access the passed parameters since
  307.                    it is part of the aEval() function.  We'll just compare the filename
  308.                    from the dictionary with each filename in the array.  If we find a
  309.                    match, then the source pointer is updated with the array offset.
  310.                    Then do the same thing for the target array.
  311.                 */
  312.  
  313.                 // initialize vars
  314.                 nSrcPtr := 0
  315.                 nTgtPtr := 0
  316.                 lDoCopy := .F.
  317.  
  318.                 // scan the source array, set nSrcPtr if filename is located
  319.                 aEval( aDirSrc, {| File, nIndex | ;
  320.                         if( alltrim(upper(cFileName)) == file[F_NAME], ;
  321.                                 nSrcPtr := nIndex, NIL )})
  322.  
  323.                 // scan the target array, set nTgtPtr if filename is located
  324.                 aEval( aDirTgt, {| File, nIndex | ;
  325.                         if( alltrim(upper(cFileName)) == file[F_NAME], ;
  326.                                 nTgtPtr := nIndex, NIL )})
  327.  
  328.                 /* Begin Sequence | End Sequence blocks are logical sections of
  329.                    code that the BREAK instruction can be used to "break-out."
  330.                 */
  331.  
  332.                 BEGIN SEQUENCE
  333.  
  334.                 /* if source pointer is 0, we have a serious problem! We'll tell the
  335.                    user about it, break out of the block, and go for the next item
  336.                 */
  337.                 if nSrcPtr == 0
  338.                         alert("Source file not located as defined!")
  339.                         lDoCopy := .F.
  340.                         BREAK
  341.                 endif
  342.  
  343.                 /* if target pointer is 0, we must do a copy.  When != 0, we have to
  344.                    compare the DATE and TIME stamps on both files to determine if the
  345.                    copy should be processed.
  346.                 */
  347.                 if nTgtPtr == 0
  348.                         lDoCopy := .T.
  349.                 else
  350.                         // load subarrays
  351.                         aSrc := aDirSrc[nSrcPtr]
  352.                         aTgt := aDirTgt[nTgtPtr]
  353.  
  354.                         // check dates; if the same, compare times
  355.                         if aSrc[F_DATE] == aTgt[F_DATE]
  356.                                 lDoCopy := lTimeStp( aTgt[F_TIME], aSrc[F_TIME])
  357.                         elseif aSrc[F_DATE] > aTgt[F_DATE]
  358.                                 lDoCopy := .T.
  359.                         endif
  360.                 endif
  361.  
  362.                 /* if interactive flag is set, we must ask the user for
  363.                    permission UNLESS in Over-Ride mode
  364.                 */
  365.                 if dict->INTERACT .and. lDoCopy .and. ! lOverride
  366.                         if alert("Do you wish to synchronize file:;" + ;
  367.                                cFileName, {"YES","no "}) == 1
  368.                                 lDoCopy := .T.
  369.                         else
  370.                                 lDoCopy := .F.
  371.                         endif
  372.                 endif
  373.  
  374.                 END SEQUENCE
  375.  
  376.                 /* Do the copy if required.  This is actually done by a batch file
  377.                    which will be called after this program terminates.
  378.                 */
  379.                 if lDoCopy
  380.                         //1st normalize source path
  381.                         if right(alltrim(dict->SOURCE_DIR), 1) == "\"
  382.                                 cSrcFile := alltrim( dict->SOURCE_DIR ) + ;
  383.                                         alltrim( cFileName )
  384.                         else
  385.                                 cSrcFile := alltrim( dict->SOURCE_DIR ) + ;
  386.                                         "\" + alltrim( cFileName )
  387.                         endif
  388.  
  389.                         //2nd normalize target path
  390.                         if right(alltrim(dict->TARGET_DIR), 1) == "\"
  391.                                 cTgtFile := alltrim( dict->TARGET_DIR ) + ;
  392.                                         alltrim( cFileName )
  393.                         else
  394.                                 cTgtFile := alltrim( dict->TARGET_DIR ) + ;
  395.                                         "\" + alltrim( cFileName )
  396.                         endif
  397.  
  398.                         /* 3rd normalize the daisy-chain path... this path, if non-
  399.                            blank will be the target for the previous target file. 
  400.                         */
  401.                         if !empty( dict->SHADOW_DIR ) .and. ;
  402.                                 right(alltrim(dict->SHADOW_DIR), 1) == "\"
  403.                                 cSHADOWFl := alltrim( dict->SHADOW_DIR ) + ;
  404.                                         alltrim( cFileName )
  405.                         elseif !empty( dict->SHADOW_DIR )
  406.                                 cSHADOWFl := alltrim( dict->SHADOW_DIR ) + ;
  407.                                         "\" + alltrim( cFileName )
  408.                         else
  409.                                 cSHADOWFl := ""
  410.                         endif
  411.  
  412.                         /* Check to see if any files are in the target directory. To
  413.                            do this, we'll use Clippers file() function with the file
  414.                            specification "NUL" which exists in all DOS directories.
  415.                            If not, build the directory(ies) and place name in array
  416.                            if not unique. This check avoids multiple MKDIRs to the
  417.                            same directory. A typical directory drive:\path\path\path
  418.                            will require three MKDIRs to be issued to DOS to insure
  419.                            that the structure is built correctly: 
  420.                                    mkdir drive:\path
  421.                                    mkdir drive:\path\path
  422.                                    mkdir drive:\path\path\path
  423.  
  424.                            We'll handle this mess by creating a null array and passing
  425.                            the path to depth() which will deal with the substring
  426.                            extractions. An element will be created for each required path.
  427.  
  428.                               ***     REMOVE THE CHECK FOR FILE() IF THE      ***
  429.                               ***     SHADOW DIRECTORY IS VIRTURAL AND        ***
  430.                               ***     YOU CANNOT ENSURE THAT THE DRIVE WILL   ***     
  431.                               ***     BE MOUNTED/AVAILABLE AT SYNC RUN        ***
  432.                         */
  433.                         if ! file( substr( cWildTgt, 1, len( cWildTgt ) - 3) + "NUL." )
  434.                                 if ascan( aMakDir, upper( alltrim( dict->TARGET_DIR ))) == 0 
  435.                                         aMakeArray := {} // initialize
  436.                                         aMakeArray := depth( cWildTgt )
  437.  
  438.                                         for nLooper := 1 to len( aMakeArray )
  439.                                                 // only create necessary level(s)
  440.                                                 if ascan( aMakDir, aMakeArray[ nLooper ] ) == 0
  441.                                                         // need to create a directory
  442.                                                         aadd( aWriteOut, "MKDIR " + ;
  443.                                                                 aMakeArray[ nLooper ])
  444.                                                         aadd( aMakDir, aMakeArray[ nLooper ] )
  445.                                                 endif
  446.                                         next
  447.                                 endif
  448.                         endif
  449.  
  450.                         /*
  451.                               ***     REMOVE THE CHECK FOR FILE() IF THE      ***
  452.                               ***     SHADOW DIRECTORY IS VIRTURAL AND        ***
  453.                               ***     YOU CANNOT ENSURE THAT THE DRIVE WILL   ***     
  454.                               ***     BE MOUNTED/AVAILABLE AT SYNC RUN        ***
  455.                         */
  456.                         if !empty( cSHADOWFl ) .and. ;
  457.                           !file( substr( cWildSHADOW, 1, len( cWildSHADOW ) - 4) + "NUL." )
  458.                                 if ascan( aMakDir, upper( alltrim( dict->SHADOW_DIR ))) == 0 
  459.                                         aMakeArray := {} // zap the array
  460.                                         aMakeArray := depth( cWildSHADOW )
  461.                                         for nLooper := 1 to len( aMakeArray )
  462.                                                 if ascan( aMakDir, aMakeArray[ nLooper ] ) == 0
  463.                                                         aadd( aWriteOut, "MKDIR " + ;
  464.                                                                 aMakeArray[ nLooper ])
  465.                                                         aadd( aMakDir, aMakeArray[ nLooper ] )
  466.                                                 endif
  467.                                         next
  468.                                 endif
  469.                         endif
  470.  
  471.                         /* create an entry in the output array for a single DOS copy
  472.                            and increment the file update counter
  473.                         */
  474.                         aadd( aWriteOut, "COPY " + CSRCFILE + " " + CTGTFILE )
  475.                         ++nCount
  476.  
  477.                         // handle the copy to a shadow directory copy if necessary
  478.                         if !empty( cSHADOWFl )
  479.                                 aadd( aWriteOut, "COPY " + CTGTFILE + " " + CSHADOWFL )
  480.                                 ++nCount
  481.                         endif
  482.                 endif
  483.  
  484.               next i  // continue loop
  485.  
  486.               // now update the dictionary to show current date and time
  487.               replace LASTDATE with date(), LASTTIME with time()
  488.  
  489.               if lTime
  490.                 aadd( eTime, "SkipRec:" + time() + " Item#" + ;
  491.                         str( nCount, 3, 0 ))
  492.               endif
  493.  
  494.               skip // to next dictionary database specification record
  495.         Enddo
  496.  
  497. WrapItUp( cOutBatch, nCount )
  498. dbCloseALL()            // close database, purge buffers
  499. erase c:\temp.$$$       // house clean...
  500.  
  501. //report status if not in quiet mode
  502. if ! lQuiet .and. nCount > 0
  503.         ? str(nCount,3,0),"files require updating!"
  504.         ? "Process batch file: ", cOutBatch
  505. endif
  506.  
  507. // End-Of-Program SYNC.EXE   No return value to DOS
  508. return NIL
  509.  
  510. function lTimeStp( cTime1, cTime2 )
  511.         // character format as hh:mm:ss
  512.         // calc as cT2 - cT1
  513.         // return True if 2 > 1
  514.         local T1h := val(substr(cTime1,1,2)) * 3600
  515.         local T1m := val(substr(cTime1,4,2)) * 60
  516.         local T1s := val(substr(cTime1,7,2)) + T1m + T1h
  517.         local T2h := val(substr(cTime2,1,2)) * 3600
  518.         local T2m := val(substr(cTime2,4,2)) * 60
  519.         local T2s := val(substr(cTime2,7,2)) + T2m + T2h
  520.         local lValue := T2s > T1s
  521.         return (lValue)
  522.  
  523. function mEdit()
  524.         local cOldColour, adBase := {}
  525.         local lRetVal := .F.
  526.  
  527.         if file("C:\SYNC.MEM")
  528.                 restore from c:\sync additive   // D_LOOK
  529.                 D_LOOK := left(D_LOOK + space(60), 60)
  530.         else
  531.                 D_LOOK := space(60)
  532.         endif
  533.  
  534.         clear screen
  535.         @ 10, 5 say "File C:\SYNC.MEM must contain the fully specified path to"
  536.         @ 11, 5 say "the location of the synchronization database: SYNC.DBF"
  537.         @ 12, 5 say "Example:    Z:\PUBLIC\MISC\SYNC.DBF"
  538.         @ 15, 5 say "Location:" get D_LOOK picture "@!"
  539.         read
  540.  
  541.         save all like D_LOOK to C:\SYNC.MEM
  542.  
  543.         @ 17, 5 say "--->  Memory Pointer Created/Updated"
  544.  
  545.         // now ask if the database needs creating at proper location
  546.         if ! file( D_LOOK) .and. ;
  547.                 alert("Create the database structure now?", ;
  548.                 {"YES","no "} ) == 1
  549.                 aadd( adBase, { "FILENAME", "C", 12, 0 })
  550.                 aadd( adBase, { "SOURCE_DIR", "C", 40, 0 })
  551.                 aadd( adBase, { "TARGET_DIR", "C", 40, 0 })
  552.                 aadd( adBase, { "SHADOW_DIR", "C", 40, 0 })
  553.                 aadd( adBase, { "INTERACT", "L",  1, 0 })
  554.                 aadd( adBase, { "LASTDATE", "D",  8, 0 })
  555.                 aadd( adBase, { "LASTTIME", "C",  8, 0 })
  556.                 dbcreate( d_LOOK, adBase )
  557.                 @ 18, 5 say "--->  Database Structure Created"
  558.                 if alert("Edit Empty Database Now?", ;
  559.                         { "YES", "no " }) == 1
  560.                         use ( D_LOOK ) new
  561.                         dEdit()
  562.                         dbCloseArea()
  563.                         lRetVal := .T.
  564.                 endif
  565.         else
  566.                lRetVal := .T.
  567.         endif
  568.         return ( lRetVal )
  569.  
  570. function depth( cPathString )
  571.         /* DOS is brain dead in some respects.  This routine will create an
  572.            array which will have one element for each subordinate directory
  573.            nesting to the lowest level.  For example, if the string passed was
  574.                 c:\windows\system\stuff\*.*
  575.            then the array would be returned as:
  576.                 [1]     c:\windows
  577.                 [2]     c:\windows\system
  578.                 [3]     c:\windows\system\stuff
  579.            thus allowing us to create the new structure prior to attempting the
  580.            desired copies.  We'll return a pointer to the array and reassign the
  581.            name in the main routine. If the string passed was c:\*.*, then the
  582.            routine will return a null array indicating that no directories are
  583.            to be built.
  584.         */
  585.  
  586.         local aArray := {}
  587.         local cTemp := "", nLoop, cDrvPrefix := ""
  588.  
  589.         // handle the special cases here
  590.         if substr( cPathString, 2 ) == ":*.*" .or. ;
  591.            substr( cPathString, 2 ) == ":\*.*"
  592.                 RETURN aArray
  593.         endif
  594.  
  595.         /* remove the drive prefix and correct for syntax. For example, both
  596.            c:\windows and c:windows are acceptable
  597.         */
  598.         cDrvPrefix := upper( substr( cPathString, 1, 2 ))
  599.         if substr( cPathString, 3, 1 ) == "\"
  600.                 cPathString := upper( substr( cPathString, 3))
  601.         else
  602.                 cPathString := "\" + upper( substr( cPathString, 3))
  603.         endif
  604.  
  605.         /* Remove the wildcard on end of string; that is  \*.*
  606.            When complete, we'll have a path that looks like
  607.                 \WINDOWS\SYSTEM\STUFF
  608.         */
  609.         cPathString := substr( cPathString, 1, len( cPathString ) - 4 )
  610.  
  611.         /* Now add the drive prefix back to create a fully specified path with
  612.            a known (normalized) structure.  For example
  613.                 C:\WINDOWS\SYSTEM\STUFF
  614.         */
  615.         cPathString := cDrvPrefix + cPathString
  616.  
  617.         /* determine the nesting levels and initialize each array element of
  618.            receptor array. When finished the stack will be:
  619.                 C:\WINDOWS                   : handled by looping
  620.                 C:\WINDOWS\SYSTEM            : handled by looping
  621.                 C:\WINDOWS\SYSTEM\STUFF      : brute force addition after loop
  622.         */
  623.         for nLoop = 4 to len( cPathString )
  624.                 if substr( cPathString, nLoop, 1 ) == "\"
  625.                         /*
  626.                               Only build structures that are required. Look
  627.                               to see if files exist in the possible directory
  628.                               combinations.
  629.  
  630.                               REMOVE THE TEST FOR FILE() IF TARGET DRIVE IS
  631.                               VIRTURAL AND YOU CANNOT ENSURE THAT THE DRIVE
  632.                               WILL BE AVAILABLE AT RUN TIME
  633.                         */
  634.                         if !file( substr( cPathString , 1, nLoop -1 ) + "\NUL." )
  635.                               aadd( aArray, ;
  636.                                 substr( cPathString , 1, nLoop -1 ))
  637.                         endif
  638.                 endif
  639.         next
  640.  
  641.         aadd( aArray, cPathString )     // last level to create!
  642.  
  643.         RETURN ( aArray )       // return a reference
  644.  
  645. function Raw_Prn( cOut )
  646.         // handle printer in the raw!
  647.         fwrite(4, cOut, len(cOut))
  648.         return NIL
  649.  
  650. function report( lVideo )
  651.         local nLineCt := 0
  652.  
  653.         go top
  654.         do while ! eof()
  655.                 if lVideo
  656.                         if nLineCt == 0 ; clear screen ; endif
  657.                         ? "File:",dict->FILENAME, ;
  658.                           "Date:",dict->LASTDATE, ;
  659.                           "Time:",dict->LASTTIME
  660.                         ++nLineCt
  661.                         if nLineCt % 20 == 0
  662.                                 @ 24, 0 say "Press any key...."
  663.                                 inkey(0)
  664.                                 nLineCt := 0
  665.                         endif
  666.                 else
  667.                         Raw_Prn( "File: " + dict->FILENAME + ;
  668.                                  "Date: " + dtoc( dict->LASTDATE ) + ;
  669.                                  "Time: " + dict->LASTTIME + ;
  670.                                  CRLF )
  671.                         ++nLineCt
  672.                         if nLineCt % 55 == 0 ; Raw_Prn ( FF ) ; endif
  673.                 endif
  674.  
  675.                 skip
  676.                 loop
  677.         enddo
  678.         Return NIL
  679.  
  680. function WrapItUP( cOutBatch, nCount )
  681.         local nLoop
  682.  
  683.         if nCount == 0
  684.                 RETURN NIL
  685.         else
  686.                 /* Open ASCII file for text output and later batch processing. I
  687.                 could have chosen to use low-level file functions but was a bit
  688.                 lazy at this point.
  689.                 */
  690.  
  691.                 aadd( aWriteOut, "REM end-of-file. " + CRLF )
  692.  
  693.                 SET CONSOLE OFF         // disable screen echo
  694.                 set alternate to ( cOutBatch )
  695.                 set alternate ON        // echo output to text file
  696.  
  697.                 ?? aWriteOut[ 1 ]
  698.  
  699.                 for nLoop = 2 to len( aWriteOut )
  700.                         ? aWriteOut[ nLoop ]
  701.                 next
  702.  
  703.                 /* If operator used /T to analyze run time, then loop through
  704.                    that array, too
  705.                 */
  706.                 if lTime
  707.                         for nLoop = 1 to len( eTime )
  708.                                 ? "REM ", eTime[ nLoop ]
  709.                         next
  710.                         ? "Finish: " + time()
  711.                 endif
  712.  
  713.                 ? ""
  714.                 set alternate OFF       // closes ASCII output
  715.                 set alternate TO        // release file handle
  716.                 set console ON          // return video display
  717.         endif
  718.  
  719.         RETURN NIL
  720.  
  721. function dEdit()
  722.         browse()        // edit in table format [ it's OK! Now Tbrowse in 5.2b ]
  723.         pack            // remove deleted records
  724.         dbcommit()      // purge DOS buffers to disk
  725.         Return NIL
  726.